/******************************************************************************
 * $Id: list.c,v 1.149 2004/09/22 01:14:22 jimb Exp $
 * @file
 * @author  Jim Brooks http://www.jimbrooks.org
 * @brief   Functions for the clist widget that holds tags
 *          and the other clist widget that holds modules.
 *          The history clist code is at history.c.
 ******************************************************************************/

#include "common.h"
#include "list.h"

#include "widgets.h"
#include "notebook.h"
#include "text.h" 
#include "history.h"
#include "statusbar.h"
#include "handlers.h"
#include "tags.h"
#include "treefunc.h"

/******************************************************************************
 * Create the tags clist widget with scrollbars (initially unpopulated).
 * The caller is responsible for packing this widget.
 * @param   pWidgetScrolledTags (OUT/GLOBAL)
 * @param   pWidgetClistTags (OUT/GLOBAL)
 ******************************************************************************/
void
CreateTagsClistWidget( void )
{
   CreateInvisibleTagsClistWidget( &pWidgetScrolledTags,
                                   &pWidgetClistTags,
                                   tagsColumnsLayout,
                                   (PFUNC)TagsClistWidgetRowSelectCallback,
                                   (PFUNC)TagsClistWidgetRowUnselectCallback );
   return;
}

/******************************************************************************
 * Create _a_ tags clist widget with scrollbars (initially unpopulated).
 * The caller is responsible for showing and packing this widget.
 * @param   ppWidgetScrolled (OUT)
 * @param   ppWidgetClist (OUT)
 ******************************************************************************/
void
CreateInvisibleTagsClistWidget( GtkWidget** ppWidgetScrolled,
                                GtkWidget** ppWidgetClist,
                                int         columnsLayout,
                                PFUNC       RowSelectCallback,
                                PFUNC       RowUnselectCallback )
{
   gchar*		titles[4];
   int			i;
   int          linesCol;
   int          columns;

  /*
   * Currently tag lines placed always at column 3.
   */
   tagsLineColumn = 3;

   switch ( columnsLayout )
   {
      case TAG_COL_LAYOUT_TAG_TYPE_MODULE_LINE:
         titles[0] = "tag";
         titles[1] = "type";
         titles[2] = "file";
         titles[3] = "line";
         linesCol = 3;
         columns = WIDGET_TAGS_CLIST_COLUMNS;
         break;

      case TAG_COL_LAYOUT_TAG_MODULE_TYPE_LINE:
         titles[0] = "tag";
         titles[1] = "file";
         titles[2] = "type";
         titles[3] = "line";
         linesCol = 3;
         columns = WIDGET_TAGS_CLIST_COLUMNS;
         break;

      case TAG_COL_LAYOUT_MODULE_TAG_TYPE_LINE:
         titles[0] = "file";
         titles[1] = "tag";
         titles[2] = "type";
         titles[3] = "line";
         linesCol = 3;
         columns = WIDGET_TAGS_CLIST_COLUMNS;
         break;

      case TAG_COL_LAYOUT_MODULE_TYPE_TAG_LINE:
         titles[0] = "file";
         titles[1] = "type";
         titles[2] = "tag";
         titles[3] = "line";
         linesCol = 3;
         columns = WIDGET_TAGS_CLIST_COLUMNS;
         break;

     /*
      * For a clist containing results of a search.
      */
      case TAG_COL_LAYOUT_TAG_MODULE_LINE:
         titles[0] = "tag";
         titles[1] = "file";
         titles[2] = "line";
         titles[3] = "";
         linesCol = 2;
         columns = WIDGET_FOUND_CLIST_COLUMNS;
         break;

      default: linesCol = columns = 0; /* dummy stmt to shutup gcc */
   }

  /*
   * Create a clist widget.
   */
   if ( columnsLayout != TAG_COL_LAYOUT_TAG_MODULE_LINE )
      *ppWidgetClist = gtk_clist_new_with_titles( 4, titles );
   else
      *ppWidgetClist = gtk_clist_new_with_titles( 3, titles );

  /*
   * Set the column titles and justification.
   */
   for ( i = 0; i < columns; ++i )
   {
      int width;

      if ( *ppWidgetClist == pWidgetClistTags )
         width = widgetTagsClistWidthCol[i];
      else
      if ( *ppWidgetClist == pWidgetClistHistory )
         width = widgetHistoryClistWidthCol[i];
      else
      if ( *ppWidgetClist == pWidgetClistFound )
         width = widgetFoundClistWidthCol[i];
      else
         { g_assert(0); width = 0; /*shutup warning*/ }

      gtk_clist_set_column_width( GTK_CLIST(*ppWidgetClist),
                                  i,
                                  width );

      if ( CompareStrings( titles[i], "file" ) == 0 )
         gtk_clist_set_column_justification( GTK_CLIST(*ppWidgetClist),
                                             i,
                                             GTK_JUSTIFY_RIGHT );

      if ( CompareStrings( titles[i], "type" ) == 0 )
         gtk_clist_set_column_justification( GTK_CLIST(*ppWidgetClist),
                                             i,
                                             GTK_JUSTIFY_CENTER );
   }
   gtk_clist_column_titles_passive( GTK_CLIST(*ppWidgetClist) );

   gtk_clist_set_reorderable( GTK_CLIST(*ppWidgetClist),
                              TRUE );

  /*
   * Connect to the signal for when a row is selected/unselected.
   */
   gtk_signal_connect( GTK_OBJECT(*ppWidgetClist),
                       "select-row",
                       GTK_SIGNAL_FUNC(RowSelectCallback),
                       NULL );
   gtk_signal_connect( GTK_OBJECT(*ppWidgetClist),
                       "unselect-row",
                       GTK_SIGNAL_FUNC(RowUnselectCallback),
                       NULL );

  /*
   * KLUDGE: Connect to "draw" signal so that row will be
   *         be in visible area (ie not outside of scrolled area).
   */
/*
   gtk_signal_connect( GTK_OBJECT(*ppWidgetClist),
                       "draw",
                       GTK_SIGNAL_FUNC(HandlerDrawClistKludge),
                       NULL );
*/
  /*
   * Connect to key press/release event handlers.
   */
   gtk_signal_connect( GTK_OBJECT(*ppWidgetClist),
                       "key-press-event",
                       (GtkSignalFunc)HandlerKeyPressClist,
                       NULL );
   gtk_signal_connect( GTK_OBJECT(*ppWidgetClist),
                       "key-release-event",
                       (GtkSignalFunc)HandlerKeyRelease,
                       NULL );
   gtk_widget_add_events( *ppWidgetClist,
                          GDK_KEY_RELEASE_MASK );	/* to enable key release events */

  /*
   * Possibly hide the column of tag lines (which is less important).
   * But a line column should remain present in the search clist (to disambiguate multiple matches).
   */
   if ( !showLineColumn  &&  *ppWidgetClist != pWidgetClistFound )
      gtk_clist_set_column_visibility( GTK_CLIST(*ppWidgetClist), linesCol, FALSE );

  /*
   * Create a scrolled widget.
   * Pack the clist widget into the scrolled widget.
   */
   *ppWidgetScrolled = gtk_scrolled_window_new( NULL, NULL );
   gtk_widget_set_usize( GTK_WIDGET(*ppWidgetScrolled),
                         WIDGET_TAGS_CLIST_WIDTH_HORZ,
                         WIDGET_TAGS_CLIST_WIDTH_VERT );
   gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(*ppWidgetScrolled),
                                   GTK_POLICY_AUTOMATIC,
                                   GTK_POLICY_AUTOMATIC);
#if 0
   /* ### This restricts vertical scrolling. ### */
   /* ### Eg, vertical scrolling won't occur ### */
   /* ### for the Up/Down keys and for Find. ### */
   gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW(*ppWidgetScrolled),
                                          *ppWidgetClist );
#else
   /* This works (per "gtkfilesel.c"). */
   gtk_container_add( GTK_CONTAINER(*ppWidgetScrolled),
                      *ppWidgetClist );
#endif

  /*
   * Change its color.
   */
   ChangeStyleColorText( *ppWidgetClist,
                         &colorClistWidgetTextFg,
                         &colorClistWidgetTextBg );
   ChangeStyleColorSel( *ppWidgetClist,
                        &colorClistWidgetSelFg,
                        &colorClistWidgetSelBg );

  /*
   * Change its font.
   */
   ChangeStyleFont( *ppWidgetClist,
                    fontNameList );

   return;
}

/******************************************************************************
 * Create the modules clist widget with scrollbars (initially unpopulated).
 * The caller is responsible for packing this widget.
 * @param   pWidgetScrolledModules (OUT/GLOBAL)
 * @param   pWidgetClistModules (OUT/GLOBAL)
 ******************************************************************************/
void
CreateModulesClistWidget( void )
{
#if WIDGET_MODULES_CLIST_COLUMNS == 1
   static gchar* titles[] = { "file" };
#else
#error
#endif
   int		i;

  /*
   * Create a clist widget.
   */
   pWidgetClistModules = gtk_clist_new_with_titles( WIDGET_MODULES_CLIST_COLUMNS, titles );

  /*
   * Set the column titles.
   */
   for ( i = 0; i < WIDGET_MODULES_CLIST_COLUMNS; ++i )
   {
      gtk_clist_set_column_width( GTK_CLIST(pWidgetClistModules),
                                  i,
                                  widgetModulesClistWidthCol[i] );
   }
   gtk_clist_column_titles_passive( GTK_CLIST(pWidgetClistModules) );

   gtk_clist_set_reorderable( GTK_CLIST(pWidgetClistModules),
                              TRUE );
   

  /*
   * Connect to the signal for when a row is selected/unselected.
   */
   gtk_signal_connect( GTK_OBJECT(pWidgetClistModules),
                       "select-row",
                       GTK_SIGNAL_FUNC(ModulesClistWidgetRowSelectCallback),
                       NULL );
   gtk_signal_connect( GTK_OBJECT(pWidgetClistModules),
                       "unselect-row",
                       GTK_SIGNAL_FUNC(ModulesClistWidgetRowUnselectCallback),
                       NULL );

  /*
   * KLUDGE: Connect to "draw" signal so that row will be
  *          be in visible area (ie not outside of scrolled area).
   */
   gtk_signal_connect( GTK_OBJECT(pWidgetClistModules),
                       "draw",
                       GTK_SIGNAL_FUNC(HandlerDrawClistKludge),
                       NULL );

  /*
   * Connect to a shared key press event handler.
   */
   gtk_signal_connect( GTK_OBJECT(pWidgetClistModules),
                       "key-press-event",
                       (GtkSignalFunc)HandlerKeyPressClist,
                       NULL );
   gtk_signal_connect( GTK_OBJECT(pWidgetClistModules),
                       "key-release-event",
                       (GtkSignalFunc)HandlerKeyRelease,
                       NULL );
   gtk_widget_add_events( pWidgetClistModules,
                          GDK_KEY_RELEASE_MASK );	/* to enable key release events */

  /*
   * Create a scrolled widget.
   * Pack the clist widget into the scrolled widget.
   */
   pWidgetScrolledModules = gtk_scrolled_window_new( NULL, NULL );
   gtk_widget_set_usize( GTK_WIDGET(pWidgetScrolledModules),
                         WIDGET_MODULES_CLIST_WIDTH_HORZ,
                         WIDGET_MODULES_CLIST_WIDTH_VERT );
   gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(pWidgetScrolledModules),
                                   GTK_POLICY_AUTOMATIC,
                                   GTK_POLICY_AUTOMATIC);
#if 0
   /* ### This restricts vertical scrolling. ### */
   /* ### Eg, vertical scrolling won't occur ### */
   /* ### for the Up/Down keys and for Find. ### */
   gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW(pWidgetScrolledModules),
                                          pWidgetClistModules );
#else
   /* This works (per "gtkfilesel.c"). */
   gtk_container_add( GTK_CONTAINER(pWidgetScrolledModules),
                      pWidgetClistModules );
#endif

  /*
   * Change its color.
   */
   ChangeStyleColorText( pWidgetClistModules,
                         &colorClistWidgetTextFg,
                         &colorClistWidgetTextBg );
   ChangeStyleColorSel( pWidgetClistModules,
                        &colorClistWidgetSelFg,
                        &colorClistWidgetSelBg );

  /*
   * Change its font.
   */
   ChangeStyleFont( pWidgetClistModules,
                    fontNameList );

   return;
}

/******************************************************************************
 * Populate the tags clist widget with the tags sorted as specified.
 *
 * Can be called to re-populate.
 * Each row is associated with a tag_t struct (via the GTK "row data" mechanism).
 * If necessary the underlying tags glist will be resorted (by recreating it).
 *
 * @param   sort
 *          One of SORT_*
 * @param   pGlistTags (IO/GLOBAL)
 *          Will be repointed by a resort.
 ******************************************************************************/
void
PopulateTagsClistWidget( int sort )
{
   void PopulateTagsClistWidget___( int _sort );

  /*
   * For stability, serialize this function.
   */
   if ( busy_PopulateTagsClistWidget ) { DebugMsg( "PopulateTagsClistWidget() is busy." ); return; }
   busy_PopulateTagsClistWidget = TRUE;
   PopulateTagsClistWidget___( sort );
   busy_PopulateTagsClistWidget = FALSE;
}
void
PopulateTagsClistWidget___( int sort )
{
  /*
   * A problem that used to occur is that the statusbar message
   * would be displayed AFTER the tags were sorted, even though
   * the GTK statusbar function was called first.
   * A solution is to process all pending events after
   * calling the GTK statusbar function.
   *
   * (PrintStatusbar() now calls ProcessPendingEvents().)
   */
   PrintStatusbarNoTimeout( _("Sorting tags...") );

  /*
   * Freeze the clist widget.
   * Also, hide all clists because hypersrc will abend if a row is clicked
   * during populating.
   */
   gtk_clist_freeze( GTK_CLIST(pWidgetClistTags) );

   gtk_widget_hide( pWidgetClistTags );
   gtk_widget_hide( pWidgetClistModules );
   gtk_widget_hide( pWidgetClistHistory );

  /*
   * Clear the clist widget if it is already populated.
   */
   if ( GTK_CLIST(pWidgetClistTags)->rows )
   {
     /*
      * Remove all rows from the clist widget.
      */
      gtk_clist_clear( GTK_CLIST(pWidgetClistTags) );
   }

  /*
   * Resort the tags glist.
   */
   if ( sort != SORT_UNSORTED ) ResortTagsGlist( sort );

  /*
   * Although ResortTagsGlist() tries to clear the statusbar,
   * for whatever reason, the clearing will be delayed if hypersrc
   * has done a long sort, so display a message during a possible pause.
   */
   if ( sort != SORT_UNSORTED )
      PrintStatusbarNoTimeout( _("Completed sorting tags.  Populating list widget with sorted tags (please wait)...") );
   else
      PrintStatusbarNoTimeout( _("Populating list widget with tags (please wait)...") );

  /*
   * Populate the clist widget with the sorted tags glist.
   */
   g_list_foreach( pGlistTags,
                   AddRowToTagsClistWidget,
                   NULL );
   MostlyClearStatusbar();

  /*
   * Unfreeze the clist widget.
   */
   gtk_clist_thaw( GTK_CLIST(pWidgetClistTags) );

   gtk_widget_show( pWidgetClistTags );
   gtk_widget_show( pWidgetClistModules );
   gtk_widget_show( pWidgetClistHistory );

   return;
}

/******************************************************************************
 * Populate the modules clist widget.
 *
 * Can be called to re-populate.
 * Each row is associated with a module name (via the GTK "row data" mechanism).
 *
 * @param   pWidgetClistModules
 ******************************************************************************/
void
PopulateModulesClistWidget( void )
{
  /*
   * Freeze the clist widget.
   */
   gtk_clist_freeze( GTK_CLIST(pWidgetClistModules) );

  /*
   * Clear the clist widget if it is already populated.
   */
   if ( GTK_CLIST(pWidgetClistModules)->rows )
   {
     /*
      * Remove all rows from the clist widget.
      */
      gtk_clist_clear( GTK_CLIST(pWidgetClistModules) );
   }

  /*
   * Populate the clist widget.
   * Pass each elem in the sorted glist of module names to a callback
   * which adds each row to the clist widget.
   */
   g_list_foreach( pGlistModuleNames,
                   AddRowToModulesClistWidget,
                   NULL );

  /*
   * Unfreeze the clist widget.
   */
   gtk_clist_thaw( GTK_CLIST(pWidgetClistModules) );

   return;
}

/******************************************************************************
 * Callback for when a row is selected in the tags clist widget.
 *
 * The purpose of this callback is to load the text widget
 * with the module associated with the selected tag.
 *
 * Will automatically select the corresponding row in the modules clist,
 * thus indirectly calling ModulesClistWidgetRowSelectCallback().
 *
 * @param   pWidget
 *          Pointer to clist widget.
 * @param   row
 *          Which row was selected.
 * @param   col
 *          Which column was selected.
 * @param   GdkEventButton
 *          (not used here?)
 * @param   pData
 *          (not used here, NULL is passed)
 ******************************************************************************/
void
TagsClistWidgetRowSelectCallback( GtkWidget*      pWidget,
                                  gint            row,
                                  gint            col,
                                  GdkEventButton* pEvent,
                                  gpointer	      pData )
{
   tag_t*	pTag;
   GtkWidget*	pWidgetTextActivePrev   = pWidgetTextActive;

g_return_if_fail( row < GTK_CLIST(pWidgetClistTags)->rows );

  /*
   * Record which row is being selected and which notebook page is showing.
   */
   widgetTagsClistSelectedRow = row;
#if 0
   notebookPageWithSelectedRow = gtk_notebook_get_current_page( GTK_NOTEBOOK(pWidgetNotebook) );
#else
   notebookPageWithSelectedRow = NotebookPageTags();
#endif

  /*
   * If a row in the history clist is selected then unselect it
   * (unless history-row-select is being handled).
   */
   if ( ! activeHistoryRowSelect )
      UnselectClistRow( pWidgetClistHistory,
                        &widgetHistoryClistSelectedRow );

  /*
   * Prepare to load the module containing the tag, and move the cursor over the tag.
   *
   * Obtain the pointer to the tag_t struct that is associated
   * with the selected row in the tags clist widget.
   */
   pTag = (tag_t*)gtk_clist_get_row_data( GTK_CLIST(pWidget), row );
g_return_if_fail( pTag );

  /*
   * Should the tagged line be shown in the main or aux text widget?
   */
   SwitchActiveTextWidget( DONT_SWITCH_UNLESS_AUX_TEXT_KEY );

  /*
   * Load the selected module (ie source file) into the text widget.
   * Move the cursor to the line that is tagged.
   */
   if ( LoadTextWidgetWithModule( pTag->pModuleName,
                                  pTag->lineInt ,   //- 1,
                                  row,
                                  DONT_RELOAD )
        != FUNC_OK )
   {
     /*
      * Oops, unable to load the module.
      * (A warning has already been displayed.)
      */
      goto quit;
   }

  /*
   * Prepend this tag into the history clist.
   * (unless history-row-select is being handled).
   */
   if ( ! activeHistoryRowSelect )
      PrependTagHistory( pTag );

  /*
   * Highlight the tag in the text widget.
   */
   HighlightTag( pTag, -1 );

  /*
   * Select the corresponding row in the modules clist.
   * Set a flag to inform the module's select_row callback to return early,
   * without affecting other colateral state.
   */
   returnEarlyModuleSelectCallback = TRUE;
   SearchClistWidget( pTag->pModuleName,
                      SEEKING_ANY_TAG,
                      NULL,
                      pWidgetClistModules,
                      0,
                      TRUE,  /* exact match */
                      FALSE, /* case-insensitive */
                      TRUE,	 /* be quiet */
                      FALSE, /* don't affect notebook */
                      TRUE   /* do select row */ );
   returnEarlyModuleSelectCallback = FALSE;

  /*
   * Clear statusbar if row was selected by user.
   */
   if ( ! rowSelectedByProgram )
      MostlyClearStatusbar();

   ScheduleUpdateFuncTree();

quit:
  /*
   * Restore the active text widget.
   */
   SetActiveTextWidget( pWidgetTextActive = pWidgetTextActivePrev );

   return;
}

/******************************************************************************
 * Callback for when a row is selected in the modules clist widget.
 *
 * The purpose of this callback is to load the text widget
 * with the module selected in the modules clist widget.
 *
 * Will return early without affecting much state (which will happen when
 * the tags row select callback selects a corresponding row in the modules clist).
 *
 * @param   pWidget
 *          Pointer to clist widget.
 * @param   row
 *          Which row was selected.
 * @param   col
 *          Which column was selected.
 * @param   GdkEventButton
 *          (not used here?)
 * @param   pData
 *          (not used here, NULL is passed)
 * @param   returnEarlyModuleSelectCallback (GLOBAL)
 *          If true then this callback will return very early,
 *          without affecting other colateral state.
 ******************************************************************************/
void
ModulesClistWidgetRowSelectCallback( GtkWidget*      pWidget,
                                     gint            row,
                                     gint            col,
                                     GdkEventButton* pEvent,
                                     gpointer        pData )
{
   char*		pModuleName;
   GtkWidget*	pWidgetTextActivePrev   = pWidgetTextActive;

g_return_if_fail( row < GTK_CLIST(pWidgetClistModules)->rows );

  /*
   * Does the tags row select callback want this callback to return early
   * (to prevent overwriting some state)?
   */
   if ( returnEarlyModuleSelectCallback )
   {
     /*
      * Just record which row is being selected, and return.
      */
      widgetModulesClistSelectedRow = row;

      return;
   }

  /*
   * Record which row was selected and which notebook page is showing.
   */
   widgetModulesClistSelectedRow = row;
#if 0
   notebookPageWithSelectedRow = gtk_notebook_get_current_page( GTK_NOTEBOOK(pWidgetNotebook) );
#else
   notebookPageWithSelectedRow = NotebookPageModules();
#endif

  /*
   * Obtain the module name that is associated with the selected row.
   */
   pModuleName = (char*) gtk_clist_get_row_data( GTK_CLIST(pWidgetClistModules),
                                                 row );
g_return_if_fail( pModuleName );

  /*
   * If a row in the history clist is selected then unselect it.
   * (unless history-row-select is being handled).
   */
   if ( ! activeHistoryRowSelect )
      UnselectClistRow( pWidgetClistHistory,
                        &widgetHistoryClistSelectedRow );

  /*
   * Should the module be shown in the main or aux text widget?
   */
   SwitchActiveTextWidget( DONT_SWITCH_UNLESS_AUX_TEXT_KEY );

  /*
   * Load the selected module (ie source file) into the text widget.
   * Move the cursor to the first line.
   */
   if ( LoadTextWidgetWithModule( pModuleName,
                                  0,
                                  ROW_NONE,
                                  DONT_RELOAD )
        != FUNC_OK )
   {
     /*
      * Oops, unable to load the module.
      * (A warning has already been displayed.)
      */
      goto quit;
   }

  /*
   * To sync the tags and modules clists, unselect the row in the tags clist.
   */
   if ( widgetTagsClistSelectedRow != ROW_NONE )
      gtk_clist_unselect_row( GTK_CLIST(pWidgetClistTags),
                              widgetTagsClistSelectedRow,
                              0 );
  /*
   * Clear statusbar if row was selected by user.
   */
   if ( ! rowSelectedByProgram )
      MostlyClearStatusbar();

   ScheduleUpdateFuncTree();

quit:
  /*
   * Restore the active text widget.
   */
   SetActiveTextWidget( pWidgetTextActive = pWidgetTextActivePrev );

   return;
}

/******************************************************************************
 * Callback for when a row is UNselected in the tags clist widget.
 ******************************************************************************/
void
TagsClistWidgetRowUnselectCallback( GtkWidget*      pWidget,
                                    gint            row,
                                    gint            col,
                                    GdkEventButton* pEvent,
                                    gpointer        pData )
{
#if 0
  /*
   * This doesn't work as intended because when a new row is selected,
   * the unselect signal for the old row is sent AFTER the select signal
   * for the new row, thus causing an incorrect assignment with ROW_NONE.
   */
   widgetTagsClistSelectedRow = ROW_NONE;
#endif

   return;
}

/******************************************************************************
 * Callback for when a row is UNselected in the modules clist widget.
 ******************************************************************************/
void
ModulesClistWidgetRowUnselectCallback( GtkWidget*      pWidget,
                                       gint            row,
                                       gint            col,
                                       GdkEventButton* pEvent,
                                       gpointer        pData )
{
#if 0
   /* ---See comment in above func--- */
   widgetModulesClistSelectedRow = ROW_NONE;
#endif

   return;
}

/*****************************************************************************
 * If a row in the tags/module/history clist is selected then unselect it.
 * Update the row variable to indicate that no row is selected.
 * @param   pWidgetClist
 *          pWidgetClist[Tags,Module,History]
 *
 * @param   pSelectedRow (IN/OUT)
 *          Pass pointer to one of widget[*]ClistSelectedRow.
 *          Will be assigned with ROW_NONE.
 *****************************************************************************/
void
UnselectClistRow( GtkWidget* pWidgetClist,
                  gint*      pSelectedRow )
{
  /*
   * Might already be unselected?
   */
   if ( *pSelectedRow != ROW_NONE )
   {
      gtk_clist_unselect_row( GTK_CLIST(pWidgetClist),
                              *pSelectedRow,
                              0 );
     /*
      * Record that no row is selected.
      */
      *pSelectedRow = ROW_NONE;
   }

   return;
}

/*****************************************************************************
 * Besides being convenient, this function is enclosed by a flag which tells
 * the row handlers that a row is being selected by program, not by user.
 * If the flag is clear then row-select handlers will clear statusbar when
 * user starts selecting rows.
 * @param   pWidgetClist
 *          pWidgetClist[Tags,Module,History]
 * @param   row
 *****************************************************************************/
void
SelectClistRow( GtkWidget* pWidgetClist,
                gint       row )
{
   gboolean rowSelectedByProgram_save;

  /*
   * There could be nesting so flag must be restored rather than cleared.
   * Outermost instance will restore FALSE.
   */
   rowSelectedByProgram_save = rowSelectedByProgram;
   rowSelectedByProgram = TRUE;

  /*
   * Move to row, ensure row is visible.
   */
   gtk_clist_moveto( GTK_CLIST(pWidgetClist),
                     row,
                     0,
                     0.5,
                     0 );
   gtk_clist_select_row( GTK_CLIST(pWidgetClist),
                         row,
                         0 );

  /*
   * Restore flag's previous value.
   */
   rowSelectedByProgram = rowSelectedByProgram_save;

   return;
}

/*****************************************************************************
 * Select a clist row as though user did.
 * Called when user clicks a toolbar button which indirectly selects a row.
 * @param   pWidgetClist
 *          pWidgetClist[Tags,Module,History]
 * @param   row
 *****************************************************************************/
void
SelectClistRowAsThoughUserDid( GtkWidget* pWidgetClist,
                               gint       row )
{
  /*
   * rowSelectedByProgram is FALSE, let it remain FALSE, so that when
   * user clicks a Later/Earlier Tag button, statusbar will be cleared.
   *
   * Move to row, ensure row is visible.
   */
   gtk_clist_moveto( GTK_CLIST(pWidgetClist),
                     row,
                     0,
                     0.5,
                     0 );
   gtk_clist_select_row( GTK_CLIST(pWidgetClist),
                         row,
                         0 );

   return;
}

/******************************************************************************
 * Using an element in the tags clist, add a row to the tags clist widget.
 * Each row is associated with a tag_t struct (via the GTK "row data" mechanism).
 * @param   pData
 *          Pointer to a tag_t struct.
 * @param   pUnused
 ******************************************************************************/
void
AddRowToTagsClistWidget( gpointer pData,
                         gpointer pUnused )
{
  /*
   * Call generic function.
   */
   AddRowToAnyTagsClistWidget( pWidgetClistTags,
                               pData,
                               pUnused,
                               1  /* append */ );
   return;
}

/******************************************************************************
 * Add a row to either the tag clist or tag-history clist.
 * Each row is associated with a tag_t struct (via the GTK "row data" mechanism).
 * Either clist will have the same column arrangement.
 * @param   pWidgetClist
 *          Either pWidgetClistTags (tag list) or pWidgetClistHistory (tag-history)
 * @param   pData
 *          Pointer to a tag_t struct.
 * @param   pUnused
 * @param   prependOrAppend
 *          0 = prepend  1 = append
 ******************************************************************************/
void
AddRowToAnyTagsClistWidget( GtkWidget* pWidgetClist,
                            gpointer   pData,
                            gpointer   pUnused,
                            gboolean   prependOrAppend )
{
   tag_t*		pTag	= pData;
   gchar*       szRow[WIDGET_TAGS_CLIST_COLUMNS];
   int			row;
   static char	strLine[30];

  /*
   * GTK+ adds to a clist a duplicate of a string passed from an app
   * (so passing this static string is OK).
   */
   sprintf( strLine, "%d", pTag->lineInt );

   PeriodicallyProcessPendingEvents();

  /*
   * Actually add a row to the clist widget.
   */
   switch ( tagsColumnsLayout )
   {
      case TAG_COL_LAYOUT_TAG_TYPE_MODULE_LINE:
         szRow[0] = pTag->pName;
         szRow[1] = pTag->pType;
         szRow[2] = pTag->pModuleName;
         szRow[3] = strLine;
         break;

      case TAG_COL_LAYOUT_TAG_MODULE_TYPE_LINE:
         szRow[0] = pTag->pName;
         szRow[1] = pTag->pModuleName;
         szRow[2] = pTag->pType;
         szRow[3] = strLine;
         break;

      case TAG_COL_LAYOUT_MODULE_TAG_TYPE_LINE:
         szRow[0] = pTag->pModuleName;
         szRow[1] = pTag->pName;
         szRow[2] = pTag->pType;
         szRow[3] = strLine;
         break;

      case TAG_COL_LAYOUT_MODULE_TYPE_TAG_LINE:
         szRow[0] = pTag->pModuleName;
         szRow[1] = pTag->pType;
         szRow[2] = pTag->pName;
         szRow[3] = strLine;
         break;

      default:
         g_assert(0);
   }

   GtkCList * pList = GTK_CLIST( pWidgetClist );

   if ( prependOrAppend == 0 )
      row = gtk_clist_prepend( pList,
                               szRow );
   else
      row = gtk_clist_append( pList,
                              szRow );

   pair_t * pIcon = GetPixmapByType( pTag->pType );
   
   gtk_clist_set_pixtext( pList,
                          row,
                          0,
                          szRow[0],
                          3,
                          (GdkPixmap*)pIcon->data1,
                          (GdkBitmap*)pIcon->data2 );
  /*
   * Associate the pointer to the tag_t struct with the row.
   * The pointer will be needed later when the user selects a row.
   */
   gtk_clist_set_row_data( pList,
                           row,
                           pData );
   return;
}


/******************************************************************************
 * Add a row to the modules clist widget.
 * Each row is associated with a module name (via the GTK "row data" mechanism).
 * @param   pModuleName
 *          Pointer to the module name.
 * @param   pUnused
 * @param   widgetModulesClistModules (IN/OUT/GLOBAL)
 *          Incremented.
 ******************************************************************************/
void
AddRowToModulesClistWidget( gpointer pModuleName,
                            gpointer pUnused )
{
   gchar*       szRow[WIDGET_MODULES_CLIST_COLUMNS];
   int		    row=0;

   if ( NULL == pModuleName )
       return;

   GtkCList *pList = GTK_CLIST( pWidgetClistModules ) ;
   
   /*
    * get the file's pixmap pic, NEVER return NULL.
    */
   pair_t *pic = GetPixmapByFileName( pModuleName );
   
  /*
   * Actually add a row to the clist widget.
   */
   szRow[0] = pModuleName;
   row = gtk_clist_append( pList ,
                           szRow );

   gtk_clist_set_pixtext( pList ,
                          row,
                          0,
                          szRow[0],
                          3,    // pading
                          (GdkPixmap*)pic->data1,
                          (GdkBitmap*)pic->data2 );
   
  /*
   * Associate the module name with the row.
   * The module name will be needed later when the user selects a row.
   */
   gtk_clist_set_row_data( pList ,
                           row,
                           pModuleName );

   return;
}

/******************************************************************************
 * Find a row in a clist widget (actually only supports the tags or modules clist).
 * If found, select the row.
 * @param   pSought
 *          Pointer to the string of what is sought.
 * @param   seekingFuncLikeTag
 *          Pass SEEKING_FUNC_LIKE_TAG if a tag of a function/macro/method is sought.
 *          Otherwise pass SEEKING_ANY_TAG.
 * @param   pModuleName
 *          Unless NULL, no tag will be matched if it doesn't reside in this module.
 * @param   pWidgetClist
 *          Pointer to a clist widget.
 *          Actually must be either "pWidgetClistTags" or "pWidgetClistModules"
 *          (will assert otherwise).
 * @param   totalRows
 *          The total amount of rows in the clist widget.
 * @param   selectedRow
 *          Which row is selected (use ROW_NONE if no row is selected).
 * @param   exact
 *          If true then only a row that exactly matches will be selected.
 *          If false, then the matching will be approximate, eg,
 *          if "bc" is sought, then "ABCD" will be an approximate match.
 * @param   caseSens
 *          N/A if "exact" is true.
 * @param   quiet
 *          If true then won't unnecessarily change the statusbar.
 * @param   selectNotebookPage
 *          If true then the appropriate notebook page will be shown.
 *          If false then the notebook is not changed.
 * @param   doSelectRow
 *          If this is true, then select row that matches.
 * @return  If found, the row in which the match was found (row will be >=0),
 *          else returns ROW_NONE.
 ******************************************************************************/
gint
SearchClistWidget( gchar*     pSought,
                   gboolean   seekingFuncLikeTag,
                   gchar*     pModuleName,
                   GtkWidget* pWidgetClist,
                   int        selectedRow,
                   int        exact,
                   int        caseSens,
                   int        quiet,
                   gboolean   selectNotebookPage,
                   gboolean   doSelectRow )
{
   int			res;
   int*			pBusy		= NULL;
   gboolean		busy_dummy  = FALSE;
   gint SearchClistWidget___( gchar*     _pSought,
                              gboolean   _seekingFuncLikeTag,
                              gchar*     _pModuleName,
                              GtkWidget* _pWidgetClist,
                              int        _selectedRow,
                              int        _exact,
                              int        _caseSens,
                              int        _quiet,
                              gboolean   _selectNotebookPage,
                              gboolean   _doSelectRow );

  /*
   * Busy status is per clist widget.
   */
   if      ( pWidgetClist == pWidgetClistTags )
      pBusy = &busy_SearchClistWidget_tag;
   else if ( pWidgetClist == pWidgetClistModules )
      pBusy = &busy_SearchClistWidget_module;
   else
   {
      pBusy = &busy_dummy;
      goto skip_busy_check;
   }

  /*
   * For stability, serialize this function.
   */
g_return_val_if_fail( pBusy, ROW_NONE );
   if ( *pBusy ) { DebugMsg( "SearchClistWidget() is busy." ); return ROW_NONE; }
   *pBusy = TRUE;
skip_busy_check:
   res = SearchClistWidget___( pSought,
                               seekingFuncLikeTag,
                               pModuleName,
                               pWidgetClist,
                               selectedRow,
                               exact,
                               caseSens,
                               quiet,
                               selectNotebookPage,
                               doSelectRow );
   *pBusy = FALSE;
   return res;
}
gint
SearchClistWidget___( gchar*     pSought,
                      gboolean   seekingFuncLikeTag,
                      gchar*     pModuleName,
                      GtkWidget* pWidgetClist,
                      int        selectedRow,
                      int        exact,
                      int        caseSens,
                      int        quiet,
                      gboolean   selectNotebookPage,
                      gboolean   doSelectRow )
{
   gint		row;
   gint     rowStarting;
   gboolean	wrapped	= FALSE;
   gboolean	matchFlag;
   gchar*	pRowKey;
   int      totalRows;
   GList*   pGlist;			/* assigned to either the modules or tags GList */
   GList*	pLink;

  /*
   * This function is specialized for only the tags or modules clist widgets.
   */
#ifdef HYPERSRC_PEDANTIC
g_return_val_if_fail( pWidgetClist == pWidgetClistTags  ||  pWidgetClist == pWidgetClistModules, ROW_NONE );
#endif

  /*
   * Init vars.
   */
   totalRows = GTK_CLIST(pWidgetClist)->rows;
   pGlist = (pWidgetClist == pWidgetClistTags) ? pGlistTags : pGlistModuleNames;

  /*
   * Print searching message.
   */
   if ( ! quiet )
   {
      if ( pWidgetClist == pWidgetClistTags )
         PrintStatusbarThirdsNoTimeout( _("Searching for tag '"),    pSought, _("'...") );
      else
         PrintStatusbarThirdsNoTimeout( _("Searching for module '"), pSought, _("'...") );
   }

  /*
   * Determine which row to start the search.
   */
   if ( selectedRow != ROW_NONE )
   {
     /*
      * Search after selected row.
      */
      rowStarting = selectedRow + 1;
      if ( rowStarting >= totalRows ) rowStarting = 0;
   }
   else
   {
     /*
      * No clist row is selected by user, start from first row.
      */
      rowStarting = 0;
   }

   if ( pWidgetClist == pWidgetClistTags )
      g_return_val_if_fail( rowStarting >= 0  &&  rowStarting < maxTags,              ROW_NONE );
   else
      g_return_val_if_fail( rowStarting >= 0  &&  rowStarting < MAX_MODULES_ABSOLUTE, ROW_NONE );

#ifdef HYPERSRC_DEBUG
   if ( pWidgetClist == pWidgetClistTags )
      g_return_val_if_fail( rowStarting >= 0  &&  rowStarting < tagsCnt, ROW_NONE );
   else
      g_return_val_if_fail( rowStarting >= 0  &&  rowStarting < g_hash_table_size(pHashModules), ROW_NONE );
#endif

  /*
   * The tags/modules clist widgets are based on underlying GLists.
   * For speed, the GList will be traversed, instead of the clist widget rows.
   * Point to a GList link corresponding to the first row to be compared,
   * which is either the selected row, or the first row if none is selected.
   */
   pLink = g_list_nth( pGlist, rowStarting );
   g_return_val_if_fail( pLink, ROW_NONE );

  /*
   * Start the search from row after the currently selected row, wrapping if necessary.
   */
   row = rowStarting;
   for (;;)
   {
      /*
       * Periodically process GTK+ events.
       */
       PeriodicallyProcessPendingEvents();

      /*
       * "row" and "pLink" now reference a valid row.
       * Compute a pointer to the row key.
       * Row data of the tags clist    contain pointers to tag_t structs.
       * Row data of the modules clist contain pointers to strings of module names.
       */
       if ( pWidgetClist == pWidgetClistTags )
          pRowKey = ((tag_t*)pLink->data)->pName;
       else
          pRowKey = pLink->data;

      /*
       * Does this row contain what is sought?
       */
       if ( exact )
          matchFlag =    0 == CompareStrings(      pRowKey, pSought );
       else
          if ( caseSens )
             matchFlag = NULL != FindSubstr( pRowKey, pSought );
          else
             matchFlag = NULL != FindSubstrI( pRowKey, pSought );

      /*
       * If requested, test if the tag with the matching name
       * resides in the required module.
       */
       if ( matchFlag  &&  pModuleName )
       {
#ifdef HYPERSRC_PEDANTIC
          g_return_val_if_fail( pWidgetClist == pWidgetClistTags, ROW_NONE );
#endif
          matchFlag = ! CompareStrings( pModuleName, (char*) ((tag_t*)pLink->data)->pModuleName );
       }

      /*
       * If requested, only match tags of funcs/methods.
       */
       if ( matchFlag  &&  seekingFuncLikeTag )
       {
          matchFlag = IsFuncLikeTag( (tag_t*)pLink->data );
       }

       if ( matchFlag )
       {
          /*
           * Found a match.
           * Clear the statusbar if quiet is false and the statusbar
           * is not displaying a search-wrapped message.
           */
           if ( ! quiet  &&  ! wrapped ) MostlyClearStatusbar();

          /*
           * Unless specified to not do so, ensure the notebook widget
           * is showing the page that contains the specified clist.
           */
           if ( selectNotebookPage )
           {
              if (    pWidgetClist == pWidgetClistTags
                   && NotebookPageTags() != gtk_notebook_get_current_page( GTK_NOTEBOOK(pWidgetNotebook)) )
                 gtk_notebook_set_page( GTK_NOTEBOOK(pWidgetNotebook),
                                        NotebookPageTags() );
              if (    pWidgetClist == pWidgetClistModules
                   && NotebookPageModules() != gtk_notebook_get_current_page( GTK_NOTEBOOK(pWidgetNotebook)) )
                 gtk_notebook_set_page( GTK_NOTEBOOK(pWidgetNotebook),
                                        NotebookPageModules() );
           }

          /*
           * Select the found row.
           */
           if ( doSelectRow )
              SelectClistRow( pWidgetClist,
                              row );

          /*
           * Conditionally clear the "searching for tag" statusbar msg.
           * Leave a wrapped message (unless quiet).
           */
           if ( ! quiet  &&  ! wrapped )
              ClearStatusbar();

          /*
           * Return the found row.
           */
           return row;
       }

      /*
       * This row did not match.
       * Move to next row and next link.
       */
       ++row;
       pLink = pLink->next;

      /*
       * Determine if wrapping back to the first row is necessary.
       */
       if ( row >= totalRows )
       {
         /*
          * Wrap back to first row/link.
          */
          row = 0;
          pLink = pGlist;

          wrapped = TRUE;

          if ( ! quiet )
          {
             if ( pWidgetClist == pWidgetClistTags )
                PrintStatusbarThirds( _("Search wrapped for tag '"),     pSought, _("' ") );
             else
                PrintStatusbarThirds( _("Search wrapped for module '"), pSought, _("' ") );
          }
       }

      /*
       * Quit if the search has done a full-circle.
       */
       if ( wrapped  &&  (row >= rowStarting) )
       {
          if ( quiet ) return ROW_NONE;

          if ( pWidgetClist == pWidgetClistTags )
          {
             if ( exact )
                PrintStatusbarThirds( _("Tag '"),    pSought, _("' not found (exact search).") );
             else
                if ( caseSens )
                   PrintStatusbarThirds( _("Tag '"),    pSought, _("' not found (fuzzy/case search).") );
                else
                   PrintStatusbarThirds( _("Tag '"),    pSought, _("' not found (fuzzy search)." ));
          }
          else
          {
             if ( exact )
                PrintStatusbarThirds( _("Module '"),    pSought, _("' not found (exact search)." ));
             else
                if ( caseSens )
                   PrintStatusbarThirds( _("Module '"),    pSought, _("' not found (fuzzy/case search)." ));
                else
                   PrintStatusbarThirds( _("Module '"),    pSought, _("' not found (fuzzy search)." ));
          }

          return ROW_NONE;
       }

       g_return_val_if_fail( pLink, ROW_NONE );

      /*
       * Continue.
       */
   }
}

/*****************************************************************************
 * @return The width of a column of a clist widget.
 *****************************************************************************/
int
ClistColumnWidth( GtkWidget* pWidgetClist,
                  int        col )
{
   g_assert( pWidgetClist );
   g_assert( col <= GTK_CLIST(pWidgetClist)->columns );

   return GTK_CLIST(pWidgetClist)->column[col].width;
}

